#!/usr/bin/env python2

import commands
import os
import sys
import socket
import time
import subprocess
import fcntl
import types
import errno
import getopt
import re
import random
import uuid

from config import Config
from utils import Exp, _dmsg, _dwarn, _derror, _get_value, _str2dict, _exec_pipe, _exec_shell, _exec_big
from diskmap import DiskMap, name2site

def _path_split(path):
    r = re.compile("/+([^/]+)/+([^/]+)/+([^/]+)")
    m = r.match(path)
    if (m == None):
        r = re.compile("/+([^/]+)/+([^/]+)")
        m = r.match(path)
        if (m == None):
            r = re.compile("/+([^/]+)")
            m = r.match(path)
    if m is None:
        return None

    return m.groups()

def _list_split(lst1, length):
    lst = []
    count = 0
    while (1):
        ll = lst1[count: count + length]
        if (len(ll) == 0):
            break;
        lst.append(ll)
        count = count + length
    return lst

def _loc2str(location):
    loc = ''
    for i in location:
        loc = loc + i + ','
    return loc[:-1]

class Storage:
    def __init__(self, config, home = None):
        self.config = config
        self.lich_admin = os.path.join(self.config.lich, "libexec/lich.admin")
        self.lich_inspect = os.path.join(self.config.lich, "libexec/lich.inspect")
        self.lich_tgt = os.path.join(self.config.lich, "libexec/lich.tgt")
        self.lich_attr = os.path.join(self.config.lich, "libexec/lich.attr")
        self.lich_snapshot = os.path.join(self.config.lich, "libexec/lich.snapshot")
        self.lich = os.path.join(self.config.lich, "libexec/lichfs")
        self.hostname = config.hostname

    def list(self, parent, inspect = True):
        if (inspect):
            cmd = self.lich_inspect
        else:
            cmd = self.lich
        if not parent.startswith('/iscsi'):
            parent = '/iscsi' + parent
        res = _exec_big([cmd, '--list', parent], 3, False)
        res = res[:-1]
        if (len(res) == 0):
            return []
        else:
            return res.split('\n')

    def list_inspect(self, path):
        cmd = self.lich_inspect
        res = _exec_big([cmd, '--list', path], 3, False)
        res = res[:-1]

        files = []
        if len(res) != 0:
            for line in res.split('\n'):
                files.append(line.split()[0])

        return files

    def query(self, parent, name):
        #cmd = self.lich_inspect + " --query '%s' %s" % (parent, name)
        res = _exec_pipe([self.lich_inspect, '--query', parent, name])[:-1]
        return res

    def isdir(self, src):
        if (src[0] == '/'):
            cid = self.chkid(src)
        else:
            cid = src
        return self._isdir(cid)

    def isfile(self, src):
        if (src[0] == '/'):
            cid = self.chkid(src)
        else:
            cid = src
        return self._isfile(cid)

    def ismultpath(self, path):
        res = _exec_pipe([self.lich_inspect, '--multipath', path], 1, False)[:-1]
        r = re.compile("multipath: (\d)", re.M)
        m = r.search(res)
        if (m == None):
            raise Exp(errno.EIO, "got bad multipath, %s, %s" % (path, rs))

        multpath = int(m.groups()[0])
        return multpath

    def _find(self, _path, _type=None, skips=None):
        assert(_type in ["vol", "pool", None])

        rs = []

        if _path in skips:
            return rs

        childs = self.list_inspect(_path)
        for c in childs:
            path = os.path.join(_path, c)

            if path in skips:
                continue

            if self.isdir(path):
                if _type in ["pool", None]:
                    rs.append(path)

                _r = self._find(path, _type, skips)
                rs.extend(_r)
            else:
                if _type in ["vol", None]:
                    rs.append(path)

        return rs

    def find(self, path, _type=None, skips=None):
        rs = []
        assert(_type in ["vol", "pool", None])

        if path in skips:
            return rs

        if self.isdir(path):
            if _type in ["pool", None]:
                rs.append(path)

            _r = self._find(path, _type, skips)
            rs.extend(_r)
        else:
            if _type in ["vol", None]:
                rs.append(path)

        return rs

    def _isdir(self, cid):
        ts = ["pool"]
        for t in ts:
            r = re.compile("%s.\d+.\d+" %(t))
            if (r.match(cid)):
                return True
        return False

    def _isfile(self, cid):
        ts = ["vol"]
        for t in ts:
            r = re.compile("%s.\d+.\d+" %(t))
            if (r.match(cid)):
                return True
        return False

    def location(self, chkid, retry = 1):
        #cmd = self.lich_inspect + " --location '%s'" % (chkid)
        res = _exec_pipe([self.lich_inspect, '--location', chkid], retry, False)[:-1]
        loc = res.split('\n')
        return loc

    def chkid(self, path):
        #cmd = self.lich_inspect + " --chkinfo %s" % (path)
        res = _exec_pipe([self.lich, '--stat', path], 1, False)[:-1]
        r = re.compile("Id: ([a-z]+).(\d+).(\d+)", re.M)
        m = r.search(res)
        if (m == None):
            raise Exp(errno.EIO, "got bad multpath, %s, %s" % (path, rs))

        chkid = "%s.%s.%s" % (m.groups()[0], m.groups()[1], m.groups()[2])
        if chkid.strip() == "0.0.null":
            raise Exp(errno.EAGAIN, "%s, chkid is null" % (path))

        return chkid

    def siteof(self, path):
        #cmd = self.lich_inspect + " --chkinfo %s" % (path)
        try:
            res = _exec_pipe([self.lich, '--attrget', path, 'storage_area_key'], 1, False)[:-1]
            site = res.strip()
        except Exp, e:
            if (e.errno == 126):
                site = ""

        return site

    def site_isnull(self, site):
        #"lich_system_null" write in cluster.h
        if (site in ["", "lich_system_null"]):
            return False

    def chkinfo(self, path):
        #return chkinfo = {"chkid": xx, "info_version": x, "disks": ['', ''], 'path': x}
        #cmd = self.lich_inspect + " --chkinfo %s" % (path)
        #parent: chunk dir.2053.0 info_version 0 @ [wu4:clean wu3:clean wu2:clean ]
        #chkinfo: chunk file.2061.0 info_version 0 @ [wu2:clean wu4:clean ]
        chkinfo = {}
        res = ""
        while True:
            try:
                res = _exec_pipe([self.lich_inspect, '--chunkstat', path], 1, False)[:-1]
                r = re.compile("unknown", re.M)
                m = r.search(res)
                if (m is not None):
                    raise Exp(errno.EAGAIN, "retry get disk name")
                break
            except Exp, e:
                if e.errno in [errno.EBUSY, errno.EAGAIN, errno.ETIMEDOUT, errno.ENONET]:
                    _dwarn(e)
                    continue

        r = re.compile("chkinfo: chunk ([a-z]+.\d+.\d+) info_version (\d+) @ \[(.+:[a-z]+ )+\]", re.M)
        m = r.search(res)
        if (m == None):
            raise Exp(errno.EIO, "got bad multpath, %s, %s" % (path, res))

        chkinfo.update({"chkid": m.groups()[0]})
        chkinfo.update({"info_version": int(m.groups()[1])})
        disks = m.groups()[2].strip().split(" ")
        chkinfo.update({"disks": disks})
        chkinfo.update({"path": path})

        return chkinfo
        #print chkinfo
        #print m.groups()

    def __recover_chunk(self, chunk):
        try:
            res = _exec_pipe([self.lich_inspect, '--recoverchunk', chunk])
        except Exp, e:
            _derror("recover chunk %s : %s" % (chunk, e.err))
            raise

    def __move_chunk(self, chunk, dist, tasks, force, async=True):
        flags = []
        if (async):
            flags.append('--async')
        if (force):
            flags.append('--force')

        try:
            res = _exec_pipe([self.lich_inspect, '--move', chunk, dist] + flags)
            if (async):
                res1 = _str2dict(res[:-1])
                tasks.append((chunk, dist, res1['worker'], res1['taskid']))
        except Exp, e:
            _derror("move chunk %s to %s : %s" % (chunk, dist, e.err))
            raise

    def move_chunks(self, lst, parallels, force):
        count = len(lst)
        finish = 0
        step = 0
        while (1):
            sub = lst[step : step + parallels]
            if (len(sub) == 0):
                break;

            task = []
            for i in sub:
                try:
                    loc = self.location(i[0], 3)
                except Exp, e:
                    if (e.errno == errno.ENOENT or e.errno == errno.EAGAIN):
                        finish = finish + 1
                        continue
                    else:
                        _derror("location %s : %s" % (i[0], e.err))
                        raise

                if (_loc2str(loc) == i[1]):
                    finish = finish + 1
                    continue
                else:
                    try:
                        self.__move_chunk(i[0], i[1], task, force)
                    except Exp, e:
                        if (e.errno == errno.ENOSPC):
                            raise
                        elif (e.errno == errno.EAGAIN):
                            try:
                                self.__recover_chunk(i[0])
                                self.__move_chunk(i[0], i[1], task, force)
                            except Exp, e:
                                _derror("move chunk %s to %s : %s" % (i[0], i[1], e.err))
                                pass
                        else:
                            _derror("move chunk %s to %s : %s" % (i[0], i[1], e.err))
            for i in task:
                try:
                    if (i[3] != '-1'):
                        res = _exec_pipe([self.lich_inspect, '--taskget', i[2], i[3]], 0)
                    finish = finish + 1
                    _dwarn("move chunk %s to %s (%d/%d)" % (i[0], i[1], finish, count))
                except Exp, e:
                    if (e.errno == errno.EBUSY or e.errno == errno.ENOENT
                        or e.errno == errno.EAGAIN or e.errno == errno.ETIMEDOUT):
                        try:
                            self.__recover_chunk(i[0])
                            self.__move_chunk(i[0], i[1], task, force, False)
                        except Exp, e:
                            _derror("move chunk %s to %s : %s" % (i[0], i[1], e.err))
                            pass
                    else:
                        _derror("move chunk %s to %s : %s" % (i[0], i[1], e.err))
                        pass

            step = step + parallels
        return count - finish #fail

    def __cast_chunk(self, chunk, dist, tasks, force, async=True):
        flags = []
        if (async):
            flags.append('--async')
        if (force):
            flags.append('--force')

        try:
            res = _exec_pipe([self.lich_inspect, '--cast', chunk, dist] + flags)
            if (async):
                res1 = _str2dict(res[:-1])
                tasks.append((chunk, dist, res1['worker'], res1['taskid']))
        except Exp, e:
            _derror("move chunk %s to %s : %s" % (chunk, dist, e.err))
            raise

    def cast_chunks(self, lst, parallels, force):
        count = len(lst)
        finish = 0
        step = 0
        while (1):
            sub = lst[step : step + parallels]
            if (len(sub) == 0):
                break;

            task = []
            for i in sub:
                try:
                    loc = self.location(i[0], 3)
                except Exp, e:
                    if (e.errno == errno.ENOENT or e.errno == errno.EAGAIN):
                        finish = finish + 1
                        continue
                    else:
                        _derror("location %s : %s" % (i[0], e.err))
                        raise

                try:
                    self.__cast_chunk(i[0], i[1], task, force)
                except Exp, e:
                    if (e.errno == errno.ENOSPC):
                        raise
                    elif (e.errno == errno.EAGAIN):
                        try:
                            self.__recover_chunk(i[0])
                            self.__cast_chunk(i[0], i[1], task, force)
                        except Exp, e:
                            _derror("cast chunk %s from %s : %s" % (i[0], i[1], e.err))
                            pass
                    else:
                        _derror("cast chunk %s from %s : %s" % (i[0], i[1], e.err))

            for i in task:
                try:
                    if (i[3] != '-1'):
                        res = _exec_pipe([self.lich_inspect, '--taskget', i[2], i[3]], 0)
                    finish = finish + 1
                    _dwarn("cast chunk %s from %s (%d/%d)" % (i[0], i[1], finish, count))
                except Exp, e:
                    if (e.errno == errno.EBUSY or e.errno == errno.ENOENT
                        or e.errno == errno.EAGAIN or e.errno == errno.ETIMEDOUT):
                        try:
                            self.__recover_chunk(i[0])
                            self.__cast_chunk(i[0], i[1], task, force)
                        except Exp, e:
                            _derror("cast chunk %s from %s : %s" % (i[0], i[1], e.err))
                            pass
                    else:
                        _derror("cast chunk %s from %s : %s" % (i[0], i[1], e.err))
                        pass

            step = step + parallels
        return count - finish #fail

    def move_objects(self, lst, parallels, force):
        fail = 0
        chunklist = []
        for obj in lst:
            #chunklist.append((obj[0], obj[1]))
            tmp = self.list(obj[0])
            for chk in tmp:
                try:
                    chunklist.append((self.query(obj[0], chk), obj[1]))
                except Exp, e:
                    if (e.errno == errno.EBUSY or e.errno == errno.ENOENT):
                        _derror("query %s[%s] : %s" % (obj[0], chk, e.err))
                        fail = fail + 1
                    else:
                        raise

        random.shuffle(chunklist)

        fail1 = self.move_chunks(chunklist, parallels, force)

        chunklist = []
        for obj in lst:
            chunklist.append((obj[0], obj[1]))

        fail2 = self.move_chunks(chunklist, parallels, force)

        return fail1 + fail + fail2

    def move_object_old(self, chkid, location, parallels, force, localize=False):
        cmd = [self.lich, '--move', chkid, location]
        if (force):
            cmd.insert(1, '--force')
        if (localize):
            cmd.insert(1, '--localize')
        _exec_pipe(cmd)
        return 0

    def move_object(self, chkid, location, force):
        disk = self.list_node(True);

        diskmap = DiskMap()
        for (k, v) in disk.items():
            if (v == 'stopped' or 'deleting' in v):
                continue;
            diskmap.add(k)

        src = self.location(chkid, 3)
        dist = location.strip().split(',')
        srclen = len(src)
        distlen = len(dist)

        if (srclen < distlen):
            if (force):
                for i in range(distlen - srclen):
                    src.append('')
            else:
                dist = dist[:srclen]
        elif (srclen > distlen and not force):
            dist = dist + src[distlen:]

        _skip = []
        for i in range(len(dist)):
            _dist = dist[i]
            _src = src[i]

            if (_src == '' or _src[:len(_dist)] != _dist):
                dist[i] = diskmap.get_in(_dist, 1, _skip)[0]
            else:
                dist[i] = _src
            _skip.append(dist[i])

        self.move_chunk(chkid, dist, True)

    def move_chunk(self, chkid, locations, force = False):
        location = ','.join(locations)
        args = [self.lich_inspect, '--movechunk', chkid, location]
        if (force):
            args.append('--force')
        return _exec_pipe(args)

    def list_object(self, chkid):
        list1 = self.list(chkid)
        list2 = []
        for i in list1:
            list2.append(self.query(chkid, i))
        return list2

    def list_node(self, verbose = False):
        res = _exec_pipe([self.lich_admin, '--listnode', '-v'], 3, False)[:-1]
        if (len(res) == 0):
            d = {}
        else:
            d = _str2dict(res)

        if (verbose):
            return d
        else:
            lst = []
            for (k, v)  in d.items():
                if (v == 'stopped'):
                    continue

                lst.append(k)
            return lst

    def stat(self, chkid, verbose=False, retry=3):
        if (verbose):
            return  _exec_big([self.lich_inspect, '--stat', chkid, '-v'], retry, False, print_result=True)
        else:
            try:
                stat = _str2dict(_exec_pipe([self.lich_inspect, '--stat', chkid], retry, False))
                #out = commands.getstatusoutput("%s --stat %s -v 2>/dev/null|grep ^chunk|wc -l"%(self.lich_inspect, chkid))
                #chunk_num = out[1].strip()
                #stat['used'] = int(stat['split']) * int(chunk_num)
                return stat
            except IndexError as err:
                print ("chkid:" + chkid)
                raise

    def nodeinfo(self, name):
        res = _exec_pipe([self.lich_admin, '--stat', name], 3, False)[:-1]
        if (len(res) == 0):
            return {}
        else:
            return _str2dict(res)

    def connection(self, name, retry = 1):
        try:
            res = _exec_pipe([self.lich_inspect, '--connection', name], retry, False)
        except Exp, e:
            if (e.errno == errno.EPERM):
                _dwarn("%s not support" % (name))
                return {}
            else:
                raise

        if (len(res) == 0):
            return {}
        else:
            return _str2dict(res)

    def create(self, path, size = 0):
        p = _path_split(path)
        if p is None:
            _derror("invalid path:%s" % path)
            raise Exp(errno.EINVAL, os.strerror(errno.EINVAL))
        if (len(p) == 3):
            if size == 0:
                _derror("--size must be specified")
                raise Exp(errno.EINVAL, os.strerror(errno.EINVAL))
            try:
                lun = str(int(p[2]))
            except Exception, e:
                _derror('invalid lun, only digit is allowed, valid lun: 0~254')
                raise Exp(errno.EINVAL, os.strerror(errno.EINVAL))
            _exec_pipe([self.lich_tgt, '--op', 'new', '--mode', 'lun', '--target',
                    str(p[1]), '--lun', lun, '--size', str(size)], 0, False)
        elif (len(p) == 2):
            _exec_pipe([self.lich_tgt, '--op', 'new', '--mode', 'target', '--target',
                    str(p[1])], 0, False)
        else:
            _derror("Not Support yet!")
            exit(1)
            _exec_pipe([self.lich, '--create', path], 0, False)
            if (len(p) == 2):
                self.attr(path, '--set', 'iscsi.protocol', 'true')

        #username, password = self.default_auth()
        #self.auth(path, username, password)

    def remove(self, path, force = False):
        p = _path_split(path)
        if p is None:
            _derror("invalid path:%s" % path)
            raise Exp(errno.EINVAL, os.strerror(errno.EINVAL))
        if (len(p) == 3):
            try:
                lun = str(int(p[2]))
            except Exception, e:
                _derror('invalid lun, only digit is allowed, valid lun: 0~254')
                raise Exp(errno.EINVAL, os.strerror(errno.EINVAL))
            _exec_pipe([self.lich_tgt, '--op', 'del', '--mode', 'lun', '--target',
                    str(p[1]), '--lun', lun], 0, False)
        elif (len(p) == 2):
            _exec_pipe([self.lich_tgt, '--op', 'del', '--mode', 'target', '--target',
                    str(p[1])], 0, False)
        else:
            _derror("Not Support yet!")
            exit(1)
            _exec_pipe([self.lich, '--create', path], 0, False)
            if (len(p) == 2):
                self.attr(path, '--set', 'iscsi.protocol', 'true')

    def scan(self, path="/", recover=False):
        s = self.config.sysstat()
        n = s["node"]
        if (n[0] == 1 and n[1] == 2):
            _dwarn("online (%u/%u), exit" % (n[0], n[1]))
            exit(errno.ENOSPC)

        cmd = self.lich + " --scan " + path
        if (recover):
            cmd = cmd + ' --recover'
        _exec_shell(cmd)

    def clone(self, src, dist, replace = False):
        #cmd = self.lich + " --clone %s %s" % (src, dist)
        if src[0] != ':' and not src.startswith('/iscsi'):
            src = '/iscsi' + src
        if dist[0] != ':' and not dist.startswith('/iscsi'):
            dist = '/iscsi' + dist
        if (replace):
            _exec_pipe([self.lich, "--replace", "--copy", src, dist], 0)
        else:
            _exec_pipe([self.lich, "--copy", src, dist], 0)

    def snapshot(self, src, dist):
        cmd = self.lich_snapshot + " --create %s %s" % (src, dist)
        _exec_shell(cmd, 0)
        self.attr(dist, '--set', 'snapshot_parent', src)

    def auth(self, path, username, password):
        cmd = self.lich + " --set username  --value %s %s" % (username, path)
        _exec_shell(cmd)
        cmd = self.lich + " --set password  --value %s %s" % (password, path)
        _exec_shell(cmd)

    def unauth(self, path):
        cmd = self.lich + " --attrremove username  %s" % (path)
        _exec_shell(cmd)
        cmd = self.lich + " --attrremove password  %s" % (path)
        _exec_shell(cmd)

    def default_auth(self):
        '''get the default auth, and set it when it was null '''
        retry = 3
        while True:
            cmd = [self.lich_attr, '--get', 'username', '/']
            cmd_ = [self.lich_attr, '--get', 'password', '/']
            p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
            ret = p.wait()
            out = p.communicate()[0]

            p_ = subprocess.Popen(cmd_, stdout=subprocess.PIPE)
            ret_ = p_.wait()
            out_ = p_.communicate()[0]

            if (ret == 0) and (ret_ == 0):
                user = out.strip().split()[-1]
                password = out_.strip().split()[-1]
                break
            elif ((ret == errno.ENOENT) or (ret_ == errno.ENOENT)):
                user = 'lich'
                password = str(uuid.uuid4())[-12:]
                self.auth("/", user, password)
                break
            elif ((ret == errno.EAGAIN) or (ret_ == errno.EAGAIN)) and retry >= 0:
                retry = retry - 1
                time.sleep(3)
            else:
                raise Exp(ret, "%s, %s"%(out, out_))
        return (user, password)

    def append(self, src, dist):
        #cmd = self.lich + " --append %s %s" % (src, dist)
        _exec_pipe([self.lich, '--append', src, dist], 0, True)

    def setmeta(self, name, force=False):
        #cmd = [self.lich_admin, '--setmeta', name]
        #if (force):
        #    cmd.append('--force')
        #_exec_pipe(cmd)
        pass

    def dropmeta(self, name, force=False):
        cmd = [self.lich_admin, '--dropmeta', name]
        if (force):
            cmd.append('--force')

        try:
            _exec_pipe(cmd)
        except Exp, e:
            if e.errno == errno.EBUSY:
                _derror("meta busy, please try again after ten minutes!")
            raise Exp(e.errno, e.err)

    #todo, a new function scan
    def recover(self, name, is_recover = False, verbose = False):
        if is_recover:
            cmd = self.lich_inspect + " --recover '%s'" % (name)
        else:
            cmd = self.lich_inspect + " --scan '%s'" % (name)

        if (verbose):
            cmd = cmd + " --verbose"

        n = 1
        while (1):
            p = subprocess.Popen(cmd, shell=True)
            ret = p.wait()
            if ret in [errno.EBUSY, errno.EAGAIN, errno.ETIMEDOUT, errno.ENONET]:
                _dwarn("recover retry: count: %s, ret: %s" % (n, os.strerror(ret)))
                n = n + 1
                time.sleep(1)
            else:
                if (ret != 0):
                    _derror("recover error: %s" % (os.strerror(ret)))
                break

    def recover_replica(self, name):
        cmd = self.lich_inspect + " --recover_replica '%s'" % (name)
        _exec_shell(cmd)

    def recoverchunk(self, name):
        cmd = self.lich_inspect + " --recoverchunk '%s'" % (name)
        _exec_shell(cmd)

    def md5(self, name):
        cmd = self.lich + " --md5 '%s'" % (name)
        _exec_shell(cmd)

    def path2id(self, path):
        #cmd = self.lich_in + " --stat '%s'" % (path)
        res = _exec_pipe([self.lich_inspect, '--stat', path])
        return _str2dict(res[:-1])['id']

    def repnum(self, path, repnum):
        _exec_pipe([self.lich_attr, '--set', 'repnum', '--value', str(repnum), path])

    def attr(self, path, attr_op, key, value):
        if (attr_op == '--set'):
            if (key == "password"):
                if (len(value) < 12 or len(value) > 16):
                    print("passwd length must in [12, 16]")
                    exit(errno.EINVAL)
            _exec_pipe([self.lich_attr, '--set', key, '--value', str(value), path], 3, False)
        else:
            _exec_shell(self.lich_attr + ' --get ' + key +  " " +path, 2, False)

    def allocate(self, path):
        if not path.startswith('/iscsi'):
            path = '/iscsi' + path
        _exec_shell(self.lich_inspect + ' --allocate ' +path, 2, False)

    def checkloc(self, path):
        res = _exec_big([self.lich_inspect, '--location', path, '-v'], 0, False)
        d = _str2dict(res)
        for (k, v)  in d.items():
            _dmsg("check %s : %s" % (k, v))
            p = v.split(',')
            a = []
            for i in p:
                a.append(i)
            s = set(a)
            if (len(s) != len(a)):
                _derror("chunk %s location error" % (k))
                exit(errno.EIO)

    def balance(self, oid):
        cmd = self.lich_inspect + " --balance '%s'" % (oid)
        _exec_shell(cmd)

def usage(unhide):
    print ("usage:")
    print (sys.argv[0] + " --create <lun path> <--size 10G>")
    print (sys.argv[0] + " --create <path>")
    print (sys.argv[0] + " --remove <path> [--force]")
    if unhide:
        print (sys.argv[0] + " [--force] --move <lun path> location1,location2,...locationN")
    print (sys.argv[0] + " --list <path>")
    print (sys.argv[0] + " [-v] --stat <path>")
    print (sys.argv[0] + " --clone :<local path> <lun path>")
    print (sys.argv[0] + " --clone <lun path> :<local file path>")
    print (sys.argv[0] + " --clone <lun path> <new lun path>")
    print (sys.argv[0] + " --snapshot <target path> <new target path>")
    if unhide:
        print (sys.argv[0] + " --append :<local file path> <lun path>")
    print (sys.argv[0] + " --md5 <lun path>")
    if unhide:
        print (sys.argv[0] + " [--recover] --scan [path]")
        print (sys.argv[0] + " --location <path>")
    print (sys.argv[0] + " --connection <lun path>")
    if unhide:
        print (sys.argv[0] + " --unauth <target path>")
    print (sys.argv[0] + " --attr --set <key>  --value <value> <path>")
    print (sys.argv[0] + " --attr --get <key> <path>")
    if unhide:
        print (sys.argv[0] + " --allocate <lun path>")
        print (sys.argv[0] + " --checkloc <path>")
        print (sys.argv[0] + " --unhide")
    print ("\nsystem keep attr:\n"
           "    username : username of iscsi target\n"
           "    password : password of iscsi target\n"
           "    iscsi.protocol : enabled iSCSI protocol, true or falue\n"
           "    repnum : repnum of lun\n"
           "    split: split size, 1 << split, [23, 27)")

    print ("\npath example:\n"
           "    storage pool: /pool1, /pool2\n"
           "    target: /pool1/target1, /pool2/target2\n"
           "    lun:  /poo1/target1/0, /pool2/target2/0\n"
           )

    if unhide:
        print ("\nlocation for move:\n"
               "    location could be rack or site\n"
               "    but not node or disk\n"
               )


def main():
    try:
        opts, args = getopt.getopt(
            sys.argv[1:],
            'hv', ['create=', 'help', 'size=', 'list=', 'remove=', 'clone=', 'snapshot=',
                   'append=', 'auth=', 'unauth=', 'listnode', 'md5=', 'verbose', 'scan=',
                   'move=', 'recover', 'location=', 'connection=', 'stat=', 'repnum=',
                   'force', 'attr', 'set=', 'value=', 'get=', 'allocate=', 'checkloc=', 'unhide']
            )
    except getopt.GetoptError, err:
        print str(err)
        usage(False)
        exit(errno.EINVAL)

    op = ""
    size = 0
    dist = ""
    src = ""
    force = False
    unhide = False
    recover = 0
    verbose = False
    key = ''
    value = ''
    attr_op = ''
    for o, a in opts:
        if o in ('--help'):
            usage(unhide)
            exit(0)
        elif o == '--unhide':
            unhide = True
            usage(unhide)
            exit(0)
        elif o in ('-v', '--verbose'):
            verbose = True
        elif o == '--create':
            op = "--create"
            dist = a
        elif o == '--remove':
            op = "--remove"
            dist = a
        elif o == '--size':
            size = a
        elif o == '--list':
            op = "--list"
            dist = a
        elif o == '--clone':
            op = "--clone"
            src = sys.argv[2]
            dist = sys.argv[3]
        elif o == '--snapshot':
            op = '--snapshot'
            src = sys.argv[2]
            dist = sys.argv[3]
        elif o == '--append':
            op = "--append"
            src = sys.argv[2]
            dist = sys.argv[3]
        elif o == '--auth':
            op = "--auth"
            path = sys.argv[2]
            username = sys.argv[3]
            password = sys.argv[4]
        elif o == '--unauth':
            op = "--unauth"
            path = sys.argv[2]
        elif o == '--listnode':
            op = "--listnode"
        elif o == '--md5':
            op = "--md5"
            dist = a
        elif o == '--scan':
            op = "--scan"
            dist = a
        elif o == '--move':
            op = o
            dist = a
        elif o == '--recover':
            recover = 1
        elif o == '--location':
            op = o
            dist = a
        elif o == '--connection':
            op = o
            dist = a
        elif o == '--stat':
            op = o
            dist = a
        elif o == '--repnum':
            op = o
            dist = a
            repnum = int(sys.argv[3])
        elif o == '--force':
            force = True
            sys.argv.remove('--force')
        elif o == '--attr':
            op = o
            dist = sys.argv[-1]
        elif o == '--set':
            key = a
            attr_op = "--set"
        elif o == '--get':
            key = a
            attr_op = "--get"
        elif o == '--value':
            value = a
        elif o == '--allocate':
            op = o
            dist = a
        elif o == '--checkloc':
            op = o
            dist = a
        else:
            assert False, 'oops, unhandled option: %s, -h for help' % o
            exit(1)

    storage = Storage(Config())
    if (op == "--create"):
        try:
            storage.create(dist, size)
        except Exp, e:
            if (e.errno != errno.EINVAL):
                _derror(e.err)
            exit(e.errno)

    elif (op == "--remove"):
        try:
            storage.remove(dist, force)
        except Exp, e:
            p = _path_split(dist);
	    if (p is not None and len(p) == 3 and e.errno == errno.ENOTEMPTY):
                _derror("Directory not empty, please try again later !")
                e.errno = errno.EAGAIN
            else:
                _derror(e.err)
            exit(e.errno)
    elif (op == "--list"):
        if not dist.startswith('/iscsi'):
            dist = '/iscsi' + dist
        try:
            list1 = storage.list(dist, True)
            for i in list1:
                print(i)
        except Exp, e:
            _derror(e.err)
            exit(e.errno)
    elif (op == "--clone"):
        try:
            storage.clone(src, dist)
        except Exp, e:
            if (e.errno != errno.EINVAL):
                _derror(e.err)
            exit(e.errno)
    elif (op == "--snapshot"):
        try:
            storage.snapshot(src, dist)
        except Exp, e:
            if (e.errno != errno.EINVAL):
                _derror(e.err)
            exit(e.errno)
    elif (op == "--append"):
        try:
            storage.append(src, dist)
        except Exp, e:
            if (e.errno != errno.EINVAL):
                _derror(e.err)
            exit(e.errno)
    elif (op == "--auth"):
        if (len(password) < 12 or len(password) > 16):
            print("passwd length must in [12, 16]")
            exit(errno.EINVAL)
        try:
            storage.auth(path, username, password)
        except Exp, e:
            _derror(e.err)
            exit(e.errno)
    elif (op == "--unauth"):
        try:
            storage.unauth(path)
        except Exp, e:
            _derror(e.err)
            exit(e.errno)
    elif (op == "--listnode"):
        res = storage.list_node(verbose)
        if (verbose):
            for (k, v)  in res.items():
                print (k + ":" + v)
        else:
            for i in res:
                print (i)
    elif (op == "--md5"):
        storage.md5(dist)
    elif (op == "--scan"):
        try:
            storage.scan(dist, recover)
        except Exp, e:
            _derror(e.err)
            exit(e.errno)
    elif (op == "--move"):
        oid = storage.path2id(dist)
        if (len(sys.argv) < 4):
            usage(unhide)
            exit(errno.EINVAL)

        storage.move_object(oid, sys.argv[3], force)

        try:
            storage.balance(oid)
        except Exp, e:
            if (e.errno == errno.ENOENT):
                pass
            else:
                raise

    elif (op == "--location"):
        try:
            res = storage.location(dist)
            for i in res:
                print (i)
        except Exp, e:
            _derror(e.err)
            exit(e.errno)
    elif (op == "--connection"):
        try:
            res = storage.connection(dist)
        except Exp, e:
            _derror(e.err)
            exit(e.errno)
        for (k, v)  in res.items():
            print ("%s:%s"  % (k, v))
    elif (op == "--stat"):
        if not dist.startswith('/iscsi'):
            dist = '/iscsi' + dist
        try:
            if (verbose):
                cmd = storage.lich_inspect + " --stat '%s' -v" % (dist)
                _exec_shell(cmd, 0, False)
                #storage.stat(dist, True)
                #print (res)
            else:
                res = storage.stat(dist)
                for (k, v)  in res.items():
                    print ("%s:%s"  % (k, v))
        except Exp, e:
            _derror(e.err)
            exit(e.errno)
    elif (op == "--repnum"):
        try:
            storage.repnum(dist, repnum)
        except Exp, e:
            _derror(e.err)
            exit(e.errno)
    elif (op == "--attr"):
        try:
            storage.attr(dist, attr_op, key, value)
        except Exp, e:
            _derror(e.err)
            exit(e.errno)
    elif (op == "--allocate"):
        try:
            storage.allocate(dist)
        except Exp, e:
            _derror(e.err)
            exit(e.errno)
    elif (op == "--checkloc"):
        try:
            storage.checkloc(dist)
        except Exp, e:
            _derror(e.err)
            exit(e.errno)
    else:
        #assert False, 'oops, unhandled option: %s, -h for help' % o
        usage(unhide)
        exit(errno.EINVAL)

if __name__ == '__main__':
    config = Config()
    if (len(sys.argv) == 1):
        usage(False)
    else:
        main()
